package cxm.proj.xtalk.smack;

import java.util.ArrayList;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.packet.Message;

import cxm.lib.sys.AsyncStartMachine;
import cxm.lib.util.MyLog;

public abstract class Call extends AsyncStartMachine implements MessageListener {
	// When callee response the caller's request
	// caller will receive onAccept()
	// when accept is true, they will receive onConnection separately
	// no matter who trigger to stop the conference
	// they will receive onCutOff() later
	public interface CallNotify {
		// whether this call is accepted, fire for caller
		public void onAccept(boolean isAccept);

		// below all fire for caller and callee
		// when connection establish
		public void onConnect();
		// the call was cut off, 
		public void onCutOff();
		public void onCallNotifyDataReceived(byte []data);
		public void onError(int errno, Object param);
	}

	private String caller = null;
	private String callee = null;
	private String selfIdentical = null; // this will be send to other side
	private String targetIdentical = null; // target identical
	private Chat chat  = null;
	private ArrayList<CallNotify> callNotifyArray = null;
	private boolean isCutOff = false;

	// should be the partial user JID
	public Call(String caller, String callee) {
		if (null == caller || null == callee ||
				"" == caller || "" == callee)
			throw new IllegalArgumentException("User");

		this.caller = caller;
		this.callee = callee;
		
		// creacte chat
		User user = UserManager.getInstance().getUser();
		if (user.getName().equals(caller))
			this.chat = user.createNewChat(callee, this);
		else
			this.chat = user.createNewChat(caller, this);
		this.callNotifyArray = new ArrayList<CallNotify>();
	}
	
	public String getCaller() {
		return this.caller;
	}

	public String getCallee() {
		return this.callee;
	}

	public Chat getChat() {
		return this.chat;
	}
	
	public String getSelfIdentical() {
		if (null == this.selfIdentical)
			throw new IllegalStateException("Identical null");
		return this.selfIdentical;
	}
	
	public void setSelfIdentical(String id) {
		if (null != this.selfIdentical)
			throw new IllegalStateException("Id set");
		this.selfIdentical = id;
	}
	
	public String getTargetIdentical() {
		if (null == this.selfIdentical)
			throw new IllegalStateException("Identical null");
		return this.targetIdentical;
	}
	
	public void setTargetIdentical(String id) {
		this.targetIdentical = id;
	}
	
	public void setCallNotify(CallNotify notify) {
		if (!this.callNotifyArray.contains(notify))
			this.callNotifyArray.add(notify);
	}
	
	public void removeCallNotify(CallNotify notify) {
		if (this.callNotifyArray.contains(notify))
			this.callNotifyArray.remove(notify);
	}
	
	private ArrayList<CallNotify> getNotifyList() {
		ArrayList<CallNotify> notifyList = new ArrayList<CallNotify>();
		for (int i = 0; i < this.callNotifyArray.size(); i++)
			notifyList.add(this.callNotifyArray.get(i));
		return notifyList;
	}
	
	public void fireOnAccept(boolean isAccept, String id) {
		if (this.callNotifyArray.size() == 0)
			throw new IllegalStateException("Nofity null");

		// set target id
		if (null != id)
			this.setTargetIdentical(id);
		ArrayList<CallNotify> notifyList = this.getNotifyList();
		for (int i = 0; i < notifyList.size(); i++)
			notifyList.get(i).onAccept(isAccept);
	}
	
	public void fireOnConnect() throws StartMachineException {
		if (this.callNotifyArray.size() == 0)
			throw new IllegalStateException("Nofity null");

		// start self
    	this.start();
		ArrayList<CallNotify> notifyList = this.getNotifyList();
    	for (int i = 0; i < notifyList.size(); i++)
			notifyList.get(i).onConnect();
	}
	
	public void fireOnCutOff() {
		if (this.callNotifyArray.size() == 0)
			throw new IllegalStateException("Nofity null");

		if (this.isCutOff) {
			MyLog.Log(MyLog.Level.Warning, "CutOff already fired");
			return;
		}
		this.isCutOff = true;

		// stop self
		this.stop();
		ArrayList<CallNotify> notifyList = this.getNotifyList();
		for (int i = 0; i < notifyList.size(); i ++)
			notifyList.get(i).onCutOff();
	}
	
	public void fireOnError(int errno, Object param) {
		if (this.callNotifyArray.size() == 0)
			throw new IllegalStateException("Nofity null");

		ArrayList<CallNotify> notifyList = this.getNotifyList();
		for (int i = 0; i < notifyList.size(); i++)
			notifyList.get(i).onError(errno, param);
	}
	
	public void fireOnDataReceived(byte []data) {
		if (this.callNotifyArray.size() == 0)
			throw new IllegalStateException("Nofity null");

		// ArrayList<CallNotify> notifyList = this.getNotifyList();
		for (int i = 0; i < this.callNotifyArray.size(); i++)
			this.callNotifyArray.get(i).onCallNotifyDataReceived(data);
	}
	
	public void finish() {
		if (null != this.chat)
			this.chat.removeMessageListener(this);
	}

	@Override
	public void processMessage(Chat arg0, Message message) {
		MyLog.Log(MyLog.Level.Debug, "Received message: " + message);
        String chatText = message.getBody();
        if (null == chatText) {
        	MyLog.Log(MyLog.Level.Error, "Message body empty");
        	return;
        }
        
		// call notify
		if (chatText.contains(CallManager.STRING_ACCEPT)) {
        	this.fireOnAccept(true, CallManager.splitIdentical(chatText));
        	// start call
        	try {
	        	// then we fire on connect
	        	this.fireOnConnect();
        	} catch (Exception e) {
        		MyLog.Log(MyLog.Level.Error, "Cannot start call: " + e);
        		this.fireOnError(CallManager.ERRNO_START_UNCAN, e.toString());
        	}
        } else if (chatText.equals(CallManager.STRING_DISACCEPT)) {
        	// fire notify
        	this.fireOnAccept(false, null);
        } else if (chatText.equals(CallManager.STRING_STOP_REQUEST)) {
        	// fire notify
        	this.fireOnCutOff();
        } else if (chatText.contains(CallManager.STRING_ERROR)) {
        	MyLog.Log(MyLog.Level.Error, "Get error, disconnecting...");
        	
        	// fire notify
        	this.fireOnError(CallManager.ERRNO_RECEIVE_ERROR, 
        			CallManager.splitIdentical(chatText));
        } else {
        	MyLog.Log(MyLog.Level.Error, "Get unknown message: " + chatText);
        }
	}
}
